(function(){
    if (typeof(YUI) === 'undefined') { alert('Error! YUI3 library is not available') }

    /**
     * Register the cometd-yui3 module
     *
     * handshake and connect/disconnect io are made directly in the method. For the other operations, they should
     * prepare the message and store in a queue, and start the io queue at the end.
     *
     * TODO:
     *  - refactor the prepareXXXMessag eto one?
     *  - refactor the queued io connection
     *
     * Connections:
     *  - Comet may use up to 2 connections
     *  - for handshake and connect, they will directly use Y.io and make new connection. As only one handshake/connect will be
     * made at a time, it will use one connection. the connect operation will maintain a long-polling connection.
     *  - for all the other request, they will use the Y.io.chain, that will queue up and make connection one by one
     */
    YUI.add("comet", function(Y) {
        var Comet = function(cfg) {
            Comet.superclass.constructor.apply(this, arguments);
            /*this.publishEvent('comet:handshake');
             *//**
             * connect is thrown right after a connection is being established. the connection may be dropped in any
             * time normally
             *//*
             this.publishEvent('comet:connect');

             *//**
             * connecting is thrown when connect request got a successful response. it's likely
             *//*
             this.publishEvent('comet:connecting');
             this.publishEvent("comet:subscribe");
             this.publishEvent("comet:publish");
             //this.publishEvent("comet:message");
             this.publishEvent("comet:unsubscribe");
             this.publishEvent("comet:disconnect");

             *//**
             * for any non-successful server response, without io error
             *//*
             this.publishEvent("comet:failure");
             *//**
             * Refer to io failure or any error
             *//*
             this.publishEvent("comet:error");*/
        };
        Comet.NAME = "comet";
        Comet.ATTRS = {
            /**
             * @attribute uriCfg
             * @description the server uri of Cometd server. writeOnce.
             *
             * templating is supported. The following variable could be used:
             * {operation}, if available
             */
            uri:{
                value:'/cometd',
                writeOnce:true
            },

            /**
             * @attribute meta
             * @description meta message properties are fixed in the Bayeux Protocol. The attribute is read-only
             * @type Object
             */
            meta:{
                value:{
                    handshake:{channel:'/meta/handshake', version:'1.0',supportedConnectionTypes:['long-polling']},
                    connect:{channel:'/meta/connect',connectionType:'long-polling'},
                    publish:{connectionType:'long-polling'},
                    subscribe:{channel:'/meta/subscribe',connectionType:'long-polling'},
                    unsubscribe:{channel:'/meta/unsubscribe'},
                    disconnect:{channel:'/meta/disconnect'}
                },
                readOnly:true
            },

            /**
             * @attribute ioCfg
             * @description base io configuration.
             */
            ioCfg:{
                value:{
                    method: "POST",
                    headers: { 'X-Transaction': 'cometd-yui3'}
                }
            },

            /**
             * @attribute ext
             * @description static ext data to be included in every message. Notice that ext could be specified in a
             * per message fashion
             * @default empty object literals
             */
            ext:{
                value:{
                    all:{},
                    handshake:{},
                    connect:{},
                    disconnect:{}
                }
            },

            /**
             * @attribute sessionID
             * @description if sessionID shall be included in the ext field of every message.
             * @type boolean|String if String, it is used as the variable name in the ext field. If true, 'sid' is used.
             * If false, no sessionID will be included in the ext.
             * @default true|'sid'
             */
            extSessionId:{
                set:function(sessionId) {
                    if (sessionId === true) return 'sid'
                    else return sessionId;
                },
                value:'sid'
            },

            /**
             * @attribute sessionCookie
             * @description the name of the SessionID Cookie.
             * @default 'JSESSIONID'
             */
            sessionIdCookie:{
                value:'JESSIONID'
            },

            /**
             * @attribute disconnectOnReconnect
             * @description after handshake, a clientId is established and associated with the browser session. The
             * clientId is removed from the server only after a 'disconnect' operation. If the user attemp to re-handshake
             * or re-connect before 'disconnect', the server will detect a 'multiple-client' and refuse to establish a
             * new clientId.<br><br>if the parameter is set to false, any handshake or connect attemp in 'connected'
             * state will be silencely ignored. If true, the new attemp will trigger a 'disconnect' operation before
             * the re-connect
             * @default false
             *
             */
            disconnectOnReconnect:{
                value: true
            },

            /**
             * @attribute disconnectBeforeUnload
             * @description register an listener to perform a disconnect when the page is unloading
             */
            disconnectBeforeUnload:{
                value:true,
                writeOnce:true
            }
        }
        Y.augment(Comet, Y.Event.Target);

        Y.extend(Comet, Y.Base, {

            /**
             * Client state as defined in section 1.5.1 of the Bayeux Protocol. The following are possible values:
             * 'unconnected', 'connecting', 'connected'
             */
            _clientState:'unconnected',
            _clientRequest:undefined,

            /**
             * Client ID as returned in a successfull handshake as defined at 4.1.2. handshake Response of the Bayeux
             * Protocol.
             */
            _clientId:undefined,


            /**
             * An incremental message ID. +1 for every outgoing message.
             */
            _lastMessageId:0,

            /**
             * outgoing message to the server
             */
            _outgoing:[],

            _queue:Y.AsyncQueue(), 
            _c2s:Y.io.queue,
            _s2c:Y.io.chain,

            /**
             * Keep track of the state of the comet client instance.
             */
            //_state:{ handshaked:false, handshaking:false,connecting:false,disconnected:false, unloaded:false},
            //TODO: review this
            _incoming:[], //_incoming and _outgoing are not used, consider to remove them
            //TODO: review this

            //TODO: review this
            _listeners:{}, //String pattern : array of listeners

            //TODO: review this

            /**
             * overriden to use substitued method
             * 0.2: no longer override the on method
             */
            //on:function() { return this.subscribeEvent.apply(this, arguments) },

            /**
             * substituted the Y.Base.publish. 'publish' refers to sending message in cometd
             */
            publishEvent:function() { return Comet.superclass.publish.apply(this, arguments); },

            /**
             * substituted the Y.Base.subscribe. 'subscribe' refers to subscribing event.
             */
            subscribeEvent:function() { return Comet.superclass.subscribe.apply(this, arguments); },

            /**
             * substituted the Y.Base.unsubscribe
             */
            unsubscribeEvent:function() { return Comet.superclass.unsubscribe.apply(this, arguments); },

            /**
             * Lifecycle method of YUI base
             */
            initializer:function(cfg) {
                Y.log(this + '.initializer() - initialized', 'debug', 'cometd-yui3');

                //register a beforeUnload listener
                if (this.get('disconnectBeforeUnload')) {
                    Y.on('beforeunload', Y.bind(function() {

                        if (this.getClientState() != 'unconnected') {
                            // prepare xhr
                            var xhr = false;
                            try { xhr = new ActiveXObject('Msxml2.XMLHTTP'); }
                            catch(err2) {
                                try { xhr = new ActiveXObject('Microsoft.XMLHTTP'); }
                                catch(err3) { try { xhr = new XMLHttpRequest(); } catch (err1) { request = false; } }
                            }

                            // perform disconnect
                            if (xhr) {
                                xhr.open("POST", this.get('uri'), false) //async = false
                                xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
                                xhr.send('message=' + Y.JSON.stringify(this._createMessage('disconnect')))
                            }

                            // remove cookie
                            Y.Cookie.remove('BAYEUX_BROWSER', {path:'/'})

                            Y.log('window.beforeunload - comet disconnected - comet: ' + comet, 'info', 'cometd-yui3')
                        }
                    }, this), window);
                }
            },

            _log:function(type, cfg, extraLogs) {
                if (!type) Y.fail('_logOperation() requires a \'type\' argument')
                cfg = cfg || {}
                switch (type) {
                    case 'handshake':
                        break;
                    default:
                        Y.log(this + '.' + type + '() - clientState: ' + this.getClientState() + ', clientId: ' + this.getClientId() +
                              ', clientRequest: ' + this._clientRequest + ', cfg: ' + (cfg ? Y.JSON.stringify(cfg) : cfg) + (extraLogs || '')
                                + ', _outgoing.length: ' + this._outgoing.length, 'debug', 'cometd-yui3');
                }

            },

            /**
             *
             * @param response an Array of messages
             */
            _handleResponse:function(response) {
                Y.log(this + '._handleResponse() - response.length: ' + (Y.Lang.isArray(response) ? response.length : 1)
                        + ', response: ' + Y.JSON.stringify(response), 'info', 'cometd-yui3')

                Y.each(response, function(m) { //for every response item
                    //Y.log(this +'._handleResponse() - r: ' + Y.JSON.stringify(r), 'debug', 'cometd-yui3')
                    this.fire('incoming', m)
                    if (!Y.Lang.isUndefined(m.successful)) { //meta message
                        if (m.successful) {
                            switch (m.channel) {
                                case '/meta/handshake':
                                    this._clientId = m.clientId;
                                    Y.each(this._outgoing, function(v) { v.clientId = m.clientId })
                                    this.fire("handshake", m);
                                    break;
                                case '/meta/connect':
                                    this._clientState = (!m.advice) ? 'unconnected' : 'connecting'
                                    this.fire('reconnect', m)
                                    break;
                                case '/meta/subscribe':
                                    this.fire('subscribe', m)
                                    break;
                                case '/meta/unsubscribe':
                                    this.fire('unsubscribe', m)
                                    break;
                            }
                            if (m.advice) { this._handleAdvice(m.advice) }

                            if (m.channel !== '/meta/handshake' && this._outgoing.length > 0) this.flush();
                        } else {
                            if (m.channel === '/meta/handshake') this._clientState = 'unconnected'

                            this.fire('failure', m); //TODO consider to add logging
                        }
                    } else { //event message
                        var listeners = this._listeners[m.channel]
                        Y.log(this + '._handleResponse() - received event message - m: ' + Y.JSON.stringify(m) +
                              ', listeners: ' + (listeners ? listeners.length : 0), 'debug', 'cometd-yui3')
                        if (listeners) { Y.each(listeners, function(listener) { listener(m) }) }
                    }
                }, this)
            },


            /**
             * handle advice. maybe called from handshake or any other messages.
             *
             * @param advice
             */
            _handleAdvice:function(advice) {
                if (!advice) {
                    Y.log(this + '._handleAdvice() - invalid advice - advice: ' + advice, 'warn', 'cometd-yui3')
                    return
                } else {
                    Y.log(this + '._handleAdvice() - advice: ' + Y.JSON.stringify(advice) + ', clientState: ' + this.getClientState() + ', clientId: ' + this.getClientId(), 'debug', 'cometd-yui3')
                }

                //if (this._state.unloaded) return;
                // if (channel == '/meta/connect') {return; } //connect advice is handled by _connected

                if (advice.reconnect && advice.reconnect == 'retry') {
                    /*if (this._clientRequest !== 'connect') {
                     Y.log(this + '._handleAdvice() - clientRequest is not connect, reconnect advice is ignored - clientRequest: ' + this._clientRequest, 'debug', 'cometd-yui3')
                     return false
                     } else*/
                    if (advice['multiple-clients']) {
                        Y.log(this + '._handleAdvice() - server detected multiple-clients, skipped', 'error', 'cometd-yui3')
                        //this._queue.add(Y.bind(this.handshake(), this))
                        return false;
                    } else if (this.getClientId() === undefined) {
                        Y.log(this + '._handleAdvice() - clientId is unavailable. it should be disconnected', 'debug', 'cometd-yui3')
                        return false;
                    } else {
                        this._clientState = 'connecting'
                        var retry = {fn:Y.bind(this.connect, this)}
                        if (!Y.Lang.isNull(advice.interval) && advice.interval != 0) {
                            retry.timeout = advice.interval
                        }
                        this._queue.add(retry);
                        this._queue.run();
                        return true;
                    }
                }
                return false;
            },
            /**
             * Perform a handshake operation as defined at section 4.1. handshake of the Bayeux Protocol.
             */
            handshake:function(cfg) {
                if (this.getClientState() === 'connecting') {
                    Y.log(this + '.handshake() - requested to handshake while it is handshaking. request is discarded', 'warn', 'cometd-yui3')
                    return;
                } else {
                    Y.log(this + '.handshake() - clientState: ' + this.getClientState() + ', cfg: ' + Y.JSON.stringify(cfg), 'debug', 'cometd-yui3')
                }

                if (this.getClientState() == 'connected') {
                    if (this.get('disconnectOnReconnect')) { this.disconnect(); } else { return;}
                }

                this._clientState = 'connecting'

                var message = Y.JSON.stringify(this._createMessage('handshake', cfg));
                Y.log(this + '.handshake() - message: ' + message, 'debug', 'cometd-yui3')

                var ioCfg = Y.merge(this.get('ioCfg'), {
                    context: this,
                    data:'message=' + message,
                    on:{
                        success:function(ioId, o) {
                            this._handleResponse(Y.JSON.parse(o.responseText))
                        },
                        failure:function(ioId, o) {
                            this._clientState = 'unconnected'
                            Y.log(this + '.handshake() - failure/io - status: ' + o.status + ', statusText: ' + o.statusText +
                                  ', clientState: ' + this._clientState, 'error', 'cometd-yui3')
                            this.fire('error', o);
                        }
                    }
                })

                this._s2c.stop();
                this._s2c(this.get('uri'), ioCfg)
                this._s2c.start();
                //this.io();
            },

            /**
             *
             * @param operation 'handshake', 'connect', 'subscribe' etc.
             * @param ext
             * @return an Array of message(s)
             */
            _createMessage:function(operation, cfg) {
                cfg = cfg || {}
                var ext = Y.merge(this.get('ext').all, this.get('ext')[operation], cfg.ext || {})
                var messages = cfg ? cfg.messages || [] : [] //TODO support message
                var result = []
                switch (operation) {
                    /**
                     * handshake message shall not include extra messages
                     */
                    case 'handshake':
                    // id, ext, and default meta.handshake
                        if (this.get('extSessionId')) ext[this.get('extSessionId')] = Y.Cookie.get(this.get('sessionIdCookie'))
                        return Y.merge({id:++this._lastMessageId, ext:ext}, this.get('meta')[operation])

                    case 'connect':
                    //a clientId is required and will be appended to the message
                        if (!this.getClientId()) { Y.log(this + '._prepareConnectMessage() - error - missing clientId', 'warn', 'cometd-yui3')}
                        if (this.get('extSessionId')) ext[this.get('extSessionId')] = Y.Cookie.get(this.get('sessionIdCookie'))
                        result.push(Y.merge({id:++this._lastMessageId, ext:ext}, this.get('meta')[operation], {clientId:this.getClientId()}))
                        break;

                    case 'subscribe':
                    //if (!this.getClientId()) { Y.log(this + '._prepareMessage() - error - missing clientId, operation: ' + operation, 'warn', 'cometd-yui3')}
                        if (this.get('extSessionId')) ext[this.get('extSessionId')] = Y.Cookie.get(this.get('sessionIdCookie'))
                        result.push(Y.merge({id:++this._lastMessageId, ext:ext}, this.get('meta')[operation], {clientId:this.getClientId(),subscription:cfg.channel}))
                        break;

                    case 'unsubscribe':
                        if (!this.getClientId()) { Y.log(this + '._prepareMessage() - error - missing clientId, operation: ' + operation, 'warn', 'cometd-yui3')}
                        if (this.get('extSessionId')) ext[this.get('extSessionId')] = Y.Cookie.get(this.get('sessionIdCookie'))
                        result.push(Y.merge({id:++this._lastMessageId, ext:ext}, this.get('meta')[operation], {clientId:this.getClientId(),subscription:cfg.channel}))
                        break;

                    case 'disconnect':
                        if (!this.getClientId()) { Y.log(this + '._prepareDisconnectMessage() - error - missing clientId', 'warn', 'cometd-yui3')}
                        if (this.get('extSessionId')) ext[this.get('extSessionId')] = Y.Cookie.get(this.get('sessionIdCookie'))
                        result.push(Y.merge({id:++this._lastMessageId, ext:ext}, this.get('meta')[operation], {clientId:this.getClientId()}))
                        break;
                }

                while (this._outgoing.length != 0) { result.push(this._outgoing.shift()) }

                return result;
            },

            _flushing:false,

            _patchMessage:function(m) {
                if (!m.id) m.id = ++this._lastMessageId;
                if (!m.clientId) m.clientId = this.getClientId()
                return m;
            },
            /**
             * New messges are added to the _outgoing queue. flushing of messages shall behave as follows:
             *  - in anytime, flushMessages may be called. if there are any active flush operation, the request is discarded.
             *  - upon flushing, all outstanding messages shall be push to the server
             *  - after flushing, new messages that are added during the last flushing shall be push to the server.
             *
             * @param message Object Literal or Array to be pushed to end of the existed Array of messages
             */
            flush:function(message) {
                /*                        Y.each(arguments, function(m) {
                 if (m && Y.Lang.isArray(m)) { this._outgoing.concat(m)}
                 else if (m && Y.Lang.isObject(m)) {this._outgoing.push(m)}
                 }, this)*/
                // push message to the end of _outgoing
                if (message && Y.Lang.isArray(message)) { this._outgoing.concat(message)}
                else if (message && Y.Lang.isObject(message)) {this._outgoing.push(message)}

                this._log('flush', {}, ', _outgoing.length: ' + this._outgoing.length + ', flushing: ' + this._flushing)

                if (this._flushing) return;


                var messages = [];
                while (this._outgoing.length != 0) { messages.push(this._outgoing.shift()) }
                this._flushing = true;

                if (messages.length <= 0) { return }

                // verify and fix messages
                Y.each(messages, function(v) { v = this._patchMessage(v)}, this)

                var ioCfg = Y.merge(this.get('ioCfg'), {
                    context: this,
                    data:'message=' + Y.JSON.stringify(messages),
                    on:{
                        success:function(ioId, o) {
                            var response = Y.JSON.parse(o.responseText)
                            this.fire("flush", response);
                            this._handleResponse(response)
                        },
                        failure:function(ioId, o) {
                            Y.log(this + '.flushMessages() - failure/io - status: ' + o.status + ', statusText: ' + o.statusText +
                                  ', clientState: ' + this._clientState, 'error', 'cometd-yui3')
                            this.fire('error', o);
                        },
                        complete:function() {
                            this._flushing = false;
                        }
                    }})
                this._c2s.stop();
                this._c2s(this.get('uri'), ioCfg)
                this._c2s.start();
            },

            /**
             * Cometd connect operation
             * @param cfg object , support ext and messages
             */
            connect:function(cfg) {
                this._log('connect', cfg || {})

                if (this.getClientState() == 'connected') {
                    if (this.get('disconnectOnReconnect')) { this.disconnect(); } else { return;}
                }

                this._clientRequest = 'connect'

                switch (this.getClientState()) {
                    case 'unconnected': //not handshaked, perform handshaking
                        Y.log(this + '.connect() - currently unconnected, to perform handshake', 'debug', 'cometd-yui3');
                        this.handshake()
                    //this.on('handshake', this.connect, this, messages) // it shall be adviced to perform connect upon handshake anyway
                        break;

                    case 'connecting': //handshaked, perform connection
                        if (this.getClientId()) { // w/ clientId, i.e. handshake successful
                            //TODO refactor to a object literal configuration
                            var messages = this._createMessage('connect', cfg)

                            var ioCfg = Y.merge(this.get('ioCfg'), {
                                context: this,
                                data:'message=' + Y.JSON.stringify(messages), //TODO enhance this to support passing in messages
                                on:{
                                    success:function(ioId, o) {
                                        this._handleResponse(Y.JSON.parse(o.responseText))
                                    },
                                    failure:function(ioId, o) {
                                        this._clientState = 'unconnected'
                                        Y.log(this + '.connect() - failure/io - status: ' + o.status + ', statusText: ' + o.statusText +
                                              ', clientState: ' + this._clientState, 'error', 'cometd-yui3')
                                        this.fire('error', o);
                                    },
                                    start:function() {
                                        this._clientState = 'connected'// set to 'connected' before long-polling connection is made
                                        this.fire("connect", cfg);
                                        Y.log(this + '.connect() - connected, if there is no immediate disconnection - clientState: ' + this._clientState +
                                              ', clientID: ' + this._clientId, 'info', 'cometd-yui3')
                                    }
                                }
                            })

                            /**
                             * Remarks: connect request shall be sent in the main thread but not queued. otherwise,
                             * the server will identify it as multiple-clients
                             */
                            this._s2c.stop();
                            this._s2c(this.get('uri'), ioCfg)
                            this._s2c.start();
                        } else { // no client id, either initial connect, or disconnect
                            if (this._clientRequest === 'connect') {
                                // if it is connecting but clientId is not available, handshaking is in progress. The
                                // current connect request is deferred for 1000ms
                                this._queue.add({fn:Y.bind(this.connect, this, messages), timeout:1000})
                            }
                        }
                        break;
                    case 'connected': break; // 'connected' is handled b4 the switch-case
                    default: Y.fail('unknown clientState - _clientState: ' + this.getClientState())
                }

                //TODO the connect message should send any message in the outgoing queue in a go
                //TODO check and send any outstanding messages added to the outgoing queue during the connect msg(?)
            },

            //TODO: review this
            removeListener:function(pattern, listener) {
                Y.each(this._listeners, function(l, p) {
                    if (p == pattern) {
                        Y.each(l, function(v, k, o) {
                            if (v == listener) o.slice(k)
                        })
                    }
                })
            },

            /**
             * As messages to batch
             * @param arguments any number of messages
             */
            batch:function() {
                Y.each(arguments, function(v) {
                    if (v && Y.Lang.isArray(v)) {
                        this._outgoing = this._outgoing.concat(v)
                    } else if (v) {
                        this._outgoing.push(v)
                    }

                }, this)
            },

            /**
             *
             * @param cfg
             *  {channel:'/mychannel', fn: .., ext: {} }
             */
            subscribe:function(cfg, msgs) {
                this._log('subscribe', cfg || {})

                if (!cfg || !cfg.channel) {
                    Y.log(this + '.subscribe() - invalid argument - cfg or channel is missing, cfg: ' + (cfg ? Y.JSON.stringify(cfg) : cfg), 'error', 'cometd-yui3');
                    return;
                }

                if (cfg.listener) {
                    if (this._listeners[cfg.channel]) this._listeners[cfg.channel].push([cfg.listener])
                    else this._listeners[cfg.channel] = [cfg.listener]
                }

                if (this.getClientState() !== 'connected') { //auto-connect
                    //Y.log(this + '.subscribe() - not connected yet, perform auto-connect, cfg: ' + (cfg ? Y.JSON.stringify(cfg) : {}), 'debug', 'cometd-yui3')
                    this.batch(this._createMessage('subscribe', cfg), msgs)
                    this.connect();
                } else {
                    this.flush(this._createMessage('subscribe', cfg), msgs);
                }


            },

            unsubscribe:function(cfg, msgs) {
                this._log('unsubscribe', cfg || {})

                if (!cfg || !cfg.channel) {
                    Y.log(this + '.unsubscribe() - invalid argument - cfg or channel is missing, cfg: ' + (cfg ? Y.JSON.stringify(cfg) : cfg), 'error', 'cometd-yui3');
                    return;
                }

                if (cfg.listener && this._listeners && this._listeners[cfg.channel]) { //remove any registered listener
                    Y.each(this._listeners[cfg.channel], function(v, k, o) {
                        Y.log('v: ' + v + ', k: ' + k + ', o: ' + o, 'warn', 'cometd-yui3')
                    })
                }

                if (this.getClientState() !== 'connected') {
                    this.batch(this._createMessage('unsubscribe', cfg), msgs);
                    this.connect()
                } else {
                    this.flush(this._createMessage('unsubscribe', cfg), msgs);
                }
            },


            /*  //TODO: review this
             send:function() {
             var channel = (arguments.length > 1) ? arguments[0] : null;
             var messages = (arguments.length > 1) ? arguments[1] : arguments[0];
             var action = 'publish';
             Y.log('send() - messages: ' + Y.JSON.stringify(messages) + ',  _state? ' + Y.JSON.stringify(this._state), 'debug', 'comet');

             var msgs = (Y.Lang.isArray(messages)) ? messages : [messages];
             if (channel) {
             Y.each(msgs, function(v) {
             if (!v.channel) v.channel = channel

             })
             }

             if (!this._state.handshaked) {
             this._queue.add(Y.bind(function() {
             this.handshake()
             }, this))
             if (!this._state.connecting) {
             this._queue.add(Y.bind(function() {
             this.connect(msgs)
             }, this))
             }
             this._queue.run();
             return;
             } else if (!this._state.connecting) {
             this._queue.add(Y.bind(function() {
             this.connect(msgs)
             }, this))
             this._queue.run();
             return;
             }
             this._sendMessages(action, msgs);
             },*/


            /**
             * Comet disconnect operation
             *
             * @param messages
             */
            disconnect:function(cfg) {
                this._log('disconnect', cfg || {}, ', _clientRequest: ' + this._clientRequest)

                if (!this.getClientId() || this.getClientState() == 'unconnected') return;

                this._clientRequest = 'disconnect'

                switch (this.getClientState()) {
                    case 'unconnected':  break;//handled before switch case
                    case 'connecting':
                        Y.log(this + '.disconnect() - connecting, schedule to disconnect after connected - clientState: ' + this.getClientState(), 'debug', 'cometd-yui3')
                        this.on('connect', this.disconnect, this)
                        break;
                    case 'connected':
                        var ioCfg = Y.merge(this.get('ioCfg'), {
                            context: this,
                            data:'message=' + Y.JSON.stringify(this._createMessage('disconnect', cfg)), //TODO enhance this to support passing in messages
                            on:{
                                success:function(ioId, o) {
                                    var json = Y.JSON.parse(o.responseText)
                                    var response = (json && Y.Lang.isArray(json) && json.length > 0) ? json[0] : null
                                    if (response && response.successful) {
                                        this._clientState = 'unconnected'
                                        Y.log(this + '.disconnect() - disconnected - clientState: ' + this._clientState +
                                              ', clientID: ' + this._clientId + ' (discarded)', 'info', 'cometd-yui3')
                                        this.fire("disconnect", response);
                                        if (response.advice) { this._handleAdvice(response.advice) }
                                    } else {
                                        Y.log(this + '.disconnect() - failure/cometd, clientState remains unchanged - responseText: ' + o.responseText +
                                              ', clientState: ' + this._clientState, 'error', 'cometd-yui3')
                                        this.fire('failure', response);
                                    }
                                },
                                failure:function(ioId, o) {
                                    this._clientState = 'unconnected'
                                    Y.log(this + '.disconnect() - failure/io - status: ' + o.status + ', statusText: ' + o.statusText +
                                          ', clientState: ' + this._clientState, 'error', 'cometd-yui3')
                                    this.fire('error', o);
                                }
                            }
                        })

                        this._c2s.stop();
                        this._c2s(this.get('uri'), ioCfg)
                        this._c2s.start();
                        Y.log(this + '.disconnect() - disconnect submitted', 'debug', 'cometd-yui3')
                        break;
                }

                /*
                 var action = 'disconnect';
                 if (!this._state.connecting || this._lock.disconnecting) return;
                 Y.log('disconnect() - disconnecting ... ' + ',  _state? ' + Y.JSON.stringify(this._state), 'info', 'comet');

                 var msg = {channel:Comet.ATTRS.disconnect.channel,clientId:this._clientId,id:this.messageId++, ext:Comet.ATTRS.disconnect.defaultExt}
                 var msgs = (messages) ? [msg].concat(messages) : [msg];
                 this._outgoing = [];
                 this._lock.disconnecting = true;
                 this._state.disconnected = false;
                 Y.log('connect() - connecting ... ', 'info', 'comet');
                 this._sendMessages(action, msgs);*/
            }
            ,

            /*
             _queueMessages:function(messages, channel) {
             messages = (!Y.Lang.isArray(messages)) ? [messages] : messages;
             for (var i = 0,m; m = messages[i]; i++) {
             var m2 = this._validateMessage(m, channel); //shall have a 'data' field
             if (m2) {
             this._outgoing.push(this._normalizeMessage(m2, channel));
             } else {
             Y.log('ignored invalid message, m: ' + Y.JSON.stringify(m), 'warn', 'comet');
             }
             }
             },
             _validateMessage:function(m, channel) { //use by _queueMessage
             if (Y.Lang.isUndefined(m) || Y.Lang.isNull(m)) return null;
             if (!m.data) return null;
             if (!channel && !m.channel) return null; //if no default channel, the message shall have a channel
             return m;
             },
             _normalizeMessage:function(m, channel) { //use by _queueMessage
             m.channel = (m.channel) ? m.channel : channel; //if 'channel' is null, set to subscribing channel
             m.ext = (m.ext) ? m.ext : Comet.ATTRS.publish.defaultExt;
             },*/


            /**
             * TODO use it in the io methods
             * TODO add logic to replace unavailable variables to ''
             * TODO add test cases
             * @param vars
             */
            _createUri:function(vars) {
                if (this.get('uri').indexof('{') == -1) {
                    return this.get('uri'); //no variable
                } else {
                    var uri = (vars) ? Y.substitute(this.get('uri'), vars) : this.get('uri')
                    return uri;
                }
            },

            /**
             * Get the current client state, refer to section 1.5.1 Client State of the Bayeux Protocol
             */
            getClientState:function() { return this._clientState; },

            /**
             * Get the current client id. return undefined before a successful handshake.
             */
            getClientId:function() { return this._clientId; },

            //TODO: review this
            destructor:function() { Y.log(this + '.destructor() - destroyed', 'debug', 'comet'); }
        });

        Y.Comet = Comet;

        /**
         * Static helper method
         */
        Y.Comet.any = function(items, fn) {
            if (Y.Lang.isArray(items)) {
                for (var k = 0,v; v = items[k]; k++) {
                    if (fn(v, k, items)) {
                        return true;
                    }
                }
            } else if (Y.Lang.isObject(items)) {
                for (var k in items) {
                    if (fn(items[k], k, items)) {
                        return true;
                    }
                }
            } else {
                Y.fail('only array or object are supported')
            }
            return false;
        }
    }, '3.1.1', {requires:['base', 'io', 'node', 'json','async-queue','cookie']});  
})();
 